package config

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"sync"
	"time"

	"ruoyi-go-echo-ddd/internal/infrastructure/persistence/prop"
	"ruoyi-go-echo-ddd/internal/infrastructure/utils"

	"github.com/fatih/color"
	"github.com/lib/pq"
	"github.com/natefinch/lumberjack"
	"github.com/rs/zerolog"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
)

type PrimaryDB struct {
	*gorm.DB
} // 主数据库类型

type SecondaryDB struct {
	*gorm.DB
} // 从数据库类型

// GlobalPgDb 全局postgres连接
var GlobalPgDb *gorm.DB

// GlobalMysqlDb 全局mysql连接
var GlobalMysqlDb *gorm.DB

// customLogger 自定义日志记录器
type customLogger struct {
	logger.Interface
	ConsoleOut                io.Writer     // 控制台输出
	FileOut                   io.Writer     // 文件输出
	IgnoreRecordNotFoundError bool          // 是否忽略记录未找到错误
	Colorful                  bool          // 是否启用彩色输出
	SlowThreshold             time.Duration // 慢sql阈值
	//ZapLogger                 *zap.Logger     // Zap 日志记录器
	ZeroLogger *zerolog.Logger // zero 日志记录器
}

// LogMode 实现日志输出的 Format 方法
//
//goland:noinspection GoUnusedParameter
func (cusLog *customLogger) LogMode(level logger.LogLevel) logger.Interface {
	newLogger := *cusLog
	return &newLogger
}

func (cusLog *customLogger) Info(ctx context.Context, msg string, data ...any) {
	cusLog.log(ctx, "INFO", msg, data...)
}

func (cusLog *customLogger) Warn(ctx context.Context, msg string, data ...any) {
	cusLog.log(ctx, "WARN", msg, data...)
}

func (cusLog *customLogger) Error(ctx context.Context, msg string, data ...any) {
	// 检查是否为记录未找到错误
	for _, arg := range data {
		if err, ok := arg.(error); ok {
			if errors.Is(err, gorm.ErrRecordNotFound) && cusLog.IgnoreRecordNotFoundError {
				// 忽略记录未找到错误
				return
			}
		}
	}
	// 记录其他错误
	cusLog.log(ctx, "ERROR", msg, data...)
}

//goland:noinspection GoUnusedParameter
func (cusLog *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	// 获取 SQL 查询语句和执行的行数
	execSql, rows := fc()
	// 获取文件名和行号
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		cusLog.log(ctx, "TRACE", "%s:%d\ngorm获取执行信息失败\n[rows:%d]\nSQL: %s\n", file, line, rows, execSql)
		return
	}

	// 计算查询的执行时间
	duration := time.Since(begin)
	// 判断是否为慢查询
	if duration > cusLog.SlowThreshold {
		cusLog.log(ctx, "WARN", "SLOW SQL >= %v\n%s:%d\n%v\n[rows:%d]\nSQL: %s\n[elapsed: %v]",
			cusLog.SlowThreshold, file, line, duration, rows, execSql, duration,
		)
	} else {
		cusLog.log(ctx, "DEBUG",
			"%s:%d\n%v\n[rows:%d]\nSQL: %s\n[elapsed: %v]",
			file, line, duration, rows, execSql, duration,
		)
	}
}

//goland:noinspection GoUnhandledErrorResult
func (cusLog *customLogger) log(ctx context.Context, level string, format string, args ...any) {
	timestamp := utils.TimeNowFormat("2006-01-02 15:04:05.000")
	message := fmt.Sprintf(format, args...)

	requestId := utils.GetValForContext(ctx, "request_id")

	// 根据日志级别设置颜色
	var logMessage string
	// 控制台输出
	if cusLog.ConsoleOut != nil {
		if cusLog.Colorful {
			switch level {
			case "INFO":
				logMessage = color.GreenString("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			case "WARN":
				logMessage = color.YellowString("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			case "ERROR":
				logMessage = color.HiRedString("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			case "TRACE":
				logMessage = color.HiBlueString("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			case "DEBUG":
				// 设置debug级别为高亮紫色
				logMessage = color.HiMagentaString("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			default:
				logMessage = fmt.Sprintf("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
			}
		} else {
			// 控制台输出，不添加颜色
			logMessage = fmt.Sprintf("%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message)
		}
		cusLog.ConsoleOut.Write([]byte(logMessage))
	}

	if cusLog.FileOut != nil {
		// 输出到文件不添加颜色
		cusLog.FileOut.Write(fmt.Appendf(nil, "%s [request_id:%s] [%s] %s\n", timestamp, requestId, level, message))
	}

	// 设置zap日志
	sprintZapLog := fmt.Sprintf("\n%s [request_id:%s] %s %s\n", timestamp, requestId, level, message)
	switch level {
	case "INFO":
		//cusLog.ZapLogger.Info(sprintZapLog)
		cusLog.ZeroLogger.Info().Str("gorm-zero日志", sprintZapLog)
	case "WARN":
		//cusLog.ZapLogger.Warn(sprintZapLog)
		cusLog.ZeroLogger.Warn().Str("gorm-zero日志", sprintZapLog)
	case "ERROR":
		//cusLog.ZapLogger.Error(sprintZapLog)
		cusLog.ZeroLogger.Error().Str("gorm-zero日志", sprintZapLog)
	case "DEBUG":
		//cusLog.ZapLogger.Debug(sprintZapLog)
		cusLog.ZeroLogger.Debug().Str("gorm-zero日志", sprintZapLog)
	case "TRACE":
		//cusLog.ZapLogger.Debug(sprintZapLog)
		cusLog.ZeroLogger.Trace().Str("gorm-zero日志", sprintZapLog)
	default:
		//cusLog.ZapLogger.Info(sprintZapLog)
		cusLog.ZeroLogger.Info().Str("gorm-zero日志", sprintZapLog)
	}
}

// initLogger 初始化日志配置
func initLogger(logName, logPath string) *lumberjack.Logger {
	currentPath, err := utils.GetCurrentPath()
	if err != nil {
		ZeroLog.Fatal().Err(err).Msg("sql初始化设置获取当前目录错误")
	}

	if currentPath == "/" {
		logPath = filepath.Join("app", logPath)
	}

	// 构建日志目录路径
	logDir := filepath.Join(logPath)
	if err := os.MkdirAll(logDir, os.ModePerm); err != nil {
		zerolog.DefaultContextLogger.Fatal().AnErr("创建日志目录失败:", err).Msg("")
	}

	// 日志文件路径
	logFileName := filepath.Join(logDir, logName)

	ZeroLog.Info().Str("sql_log_path", logFileName).Msg("sql日志路径")

	// 配置日志切割
	return &lumberjack.Logger{
		Filename:   logFileName,
		MaxSize:    10,    // MB
		MaxAge:     30,    // 保留天数
		MaxBackups: 30,    // 最大备份数
		Compress:   false, // 不压缩
		LocalTime:  true,
	}
}

// NacosPostgresConfig nacos中的postgres结构
type NacosPostgresConfig struct {
	Postgres prop.PostgresqlConfig `yaml:"postgres"`
}

// NacosMysqlConfig nacos中的mysql结构
type NacosMysqlConfig struct {
	Mysql prop.MysqlConfig `yaml:"mysql"`
}

var postgresOnce = sync.OnceValues(func() (*gorm.DB, error) {
	// 初始化日志—懒加载仅一次
	lumberjackLogger := initLogger("gorm_pg_sql_zero.log", "logs/postgres")

	// 创建自定义的 logger 实例
	customGormLogger := &customLogger{
		ConsoleOut:                os.Stdout,
		FileOut:                   lumberjackLogger,
		IgnoreRecordNotFoundError: true,
		Colorful:                  true,
		SlowThreshold:             time.Second, // 设置慢sql阈值为1秒
		//ZapLogger:                 ZapLog,      // 增加zap日志记录
		ZeroLogger: &ZeroLog,
	}

	// 构建连接字符串
	connStr := ""

	dbConfig, err := GetConfigFromNacos[NacosPostgresConfig](NacosClient, "postgres", "DEFAULT_GROUP", "yml")
	if err != nil {
		zerolog.DefaultContextLogger.Error().Err(err).Msg("从nacos获取postgres配置错误,使用默认配置")
		connStr = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
			PostgresConfig.Host, PostgresConfig.Port, PostgresConfig.User, PostgresConfig.Password, PostgresConfig.DbName)
	} else {
		postgresCfg := dbConfig.Postgres
		zerolog.DefaultContextLogger.Info().Msgf("从nacos获取postgres配置===%+v", postgresCfg)

		if postgresCfg.Host != "" && postgresCfg.User != "" && postgresCfg.Password != "" && postgresCfg.DbName != "" {
			connStr = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
				postgresCfg.Host, postgresCfg.Port, postgresCfg.User, postgresCfg.Password, postgresCfg.DbName)
		}
	}
	//zerolog.DefaultContextLogger.Info().Msgf("postgresCfg配置信息===%s", utils.ToJson(postgresCfg))
	//postgresqlItem := postgresCfg.Postgresql

	// 连接到数据库
	db, err := gorm.Open(postgres.Open(connStr), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			//TablePrefix:   "tb_", // 可以设置表前缀
			SingularTable: true, // 禁用复数表名
		},
		CreateBatchSize: 2000, // 批量创建批次条数
		PrepareStmt:     true, // 启用预编译
		//ZeroLogger: logger.Default.LogMode(logger.Info),
		Logger: customGormLogger, // 使用自定义的日志器
	})

	if err != nil {
		handlePostgresError(err)
		ZeroLog.Error().Err(err).Msg("Failed to Connect to Postgresql Database")
		return nil, err
	}

	sqlDb, err := db.DB()
	if err != nil {
		ZeroLog.Fatal().Err(err).Msg("Failed to Get the Postgresql Database")
		return nil, err
	}

	if err = sqlDb.Ping(); err != nil {
		ZeroLog.Fatal().Err(err).Msg("Failed to Ping the Postgresql")
		return nil, err
	}

	setConnPoolProps("postgres", sqlDb, false)

	ZeroLog.Info().Any("状态", sqlDb.Stats()).Msg("Gorm Create Postgresql Database Connection successful")
	return db, nil
})

// handlePostgresError 处理处理postgresL特定的错误连接错误
func handlePostgresError(err error) {
	var pqErr *pq.Error

	// 如果无法解析为pq.Error，尝试从错误消息中提取SQLSTATE
	if sqlState := extractSQLState(err.Error()); sqlState != "" {
		handleErrorBySQLState(sqlState, err)
		return
	}

	// 优先尝试解析为pq.Error
	if errors.As(err, &pqErr) {
		handlePQError(pqErr)
		return
	}

	switch {
	case errors.As(err, &pqErr):
		handlePQError(pqErr)

	case errors.Is(err, gorm.ErrInvalidDB):
		ZeroLog.Error().Msg("无效的数据库配置，请检查连接字符串")

	case errors.Is(err, gorm.ErrRecordNotFound):
		// 通常不会在Open时出现，但保留以防万一
		ZeroLog.Error().Msg("尝试打开数据库时记录未找到")

	case isConnectionError(err):
		ZeroLog.Fatal().
			Err(err).
			Msg("致命的数据库连接错误，程序将退出")

	case isAuthenticationError(err):
		ZeroLog.Error().
			Err(err).
			Msg("数据库认证失败，请检查用户名和密码")

	case isConfigurationError(err):
		ZeroLog.Error().
			Err(err).
			Msg("数据库配置错误，请检查连接参数")

	default:
		ZeroLog.Error().
			Err(err).
			Msg("未知的数据库连接错误")
	}
}

// extractSQLState 从错误消息中提取sql state代码
func extractSQLState(errMsg string) string {
	// 正则匹配 (SQLSTATE XXXXX)
	re := regexp.MustCompile(`SQLSTATE (\w{5})`)
	matches := re.FindStringSubmatch(errMsg)
	if len(matches) > 1 {
		return matches[1]
	}
	return ""
}

// handlePQError handlePQError 处理postgresL特定的错误
func handlePQError(pqErr *pq.Error) {
	errCode := string(pqErr.Code)
	switch errCode {
	case "28P01": // 无效密码
		ZeroLog.Fatal().
			Str("pg_code", errCode).
			Str("detail", pqErr.Detail).
			Msg("数据库认证失败：无效的用户名或密码")

	case "SQLSTATE 3D000": // 数据库不存在
		ZeroLog.Error().
			Str("pg_code", errCode).
			Str("database", pqErr.Message).
			Msg("指定的数据库不存在")

	case "08006": // 连接失败
		ZeroLog.Fatal().
			Str("pg_code", errCode).
			Str("host", pqErr.Hint).
			Msg("无法连接到数据库服务器")

	case "42501": // 权限不足
		ZeroLog.Error().
			Str("pg_code", errCode).
			Str("object", pqErr.Table).
			Msg("数据库访问权限不足")

	default:
		ZeroLog.Error().
			Str("pg_code", errCode).
			Str("message", pqErr.Message).
			Str("detail", pqErr.Detail).
			Str("hint", pqErr.Hint).
			Msg("PostgreSQL数据库错误")
	}
}

// 根据SQLSTATE处理错误
func handleErrorBySQLState(sqlState string, err error) {
	switch sqlState {
	case "3D000": // 数据库不存在
		ZeroLog.Error().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("指定的数据库不存在，请检查数据库名称")

	case "28P01": // 无效密码
		ZeroLog.Error().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("数据库认证失败：无效的用户名或密码")

	case "08006": // 连接失败
		ZeroLog.Fatal().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("无法连接到数据库服务器")

	case "42501": // 权限不足
		ZeroLog.Error().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("数据库访问权限不足")

	case "42P01": // 表不存在
		ZeroLog.Error().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("请求的表不存在")

	default:
		ZeroLog.Error().
			Str("sqlstate", sqlState).
			Err(err).
			Msg("数据库错误")
	}
}

// 辅助函数：判断是否为连接错误
func isConnectionError(err error) bool {
	errorMsgSlice := []string{
		"connection refused",
		"no such host",
		"connection reset by peer",
		"timeout",
		"network unreachable",
		"host not reachable",
	}

	for _, msg := range errorMsgSlice {
		if strings.Contains(strings.ToLower(err.Error()), msg) {
			return true
		}
	}
	return false
}

// 辅助函数：判断是否为认证错误
func isAuthenticationError(err error) bool {
	errorMsgs := []string{
		"authentication failed",
		"password authentication failed",
		"invalid username/password",
		"pg_hba.conf",
	}

	for _, msg := range errorMsgs {
		if strings.Contains(strings.ToLower(err.Error()), msg) {
			return true
		}
	}
	return false
}

// 辅助函数：判断是否为配置错误
func isConfigurationError(err error) bool {
	errorMsgSlice := []string{
		"invalid connection string",
		"missing \"=",
		"invalid port",
		"unknown configuration parameter",
	}

	for _, msg := range errorMsgSlice {
		if strings.Contains(strings.ToLower(err.Error()), msg) {
			return true
		}
	}
	return false
}

// InitGormPostgresConn 初始化gorm的postgres链接
//
// 返回值:
//
//	PrimaryDB: gorm的数据库信息
//	error: 错误信息
//
//goland:noinspection ALL
func InitGormPostgresConn() (PrimaryDB, error) {
	postgresConn, err := postgresOnce()
	return PrimaryDB{postgresConn}, err
}

var mysqlOnce = sync.OnceValues(func() (*gorm.DB, error) {
	// 初始化日志—懒加载仅一次
	lumberjackLogger := initLogger("gorm_mysql_zero.log", "logs/mysql")

	// 创建自定义的 logger 实例
	customGormLogger := &customLogger{
		ConsoleOut:                os.Stdout,
		FileOut:                   lumberjackLogger,
		IgnoreRecordNotFoundError: true,
		Colorful:                  true,
		SlowThreshold:             time.Second, // 设置慢sql阈值为1秒
		//ZapLogger:                 ZapLog,      // 增加zap日志记录
		ZeroLogger: &ZeroLog, // 增加zero日志记录
	}
	//  mysql连接字符串
	connStr := ""

	mysqlConfig, err := GetConfigFromNacos[NacosMysqlConfig](NacosClient, "mysql", "DEFAULT_GROUP", "yml")
	if err != nil {
		zerolog.DefaultContextLogger.Error().Err(err).Msg("从nacos获取mysql配置错误,使用默认配置")
		connStr = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=60s",
			MysqlConfig.Username, MysqlConfig.Password, MysqlConfig.Url, MysqlConfig.Port, MysqlConfig.Database)
	} else {
		mysqlCfg := mysqlConfig.Mysql
		zerolog.DefaultContextLogger.Info().Msgf("从nacos获取mysql配置===%+v", mysqlCfg)
		if mysqlCfg.Url != "" && mysqlCfg.Username != "" && mysqlCfg.Password != "" && mysqlCfg.Database != "" {
			connStr = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=60s",
				mysqlCfg.Username, mysqlCfg.Password, mysqlCfg.Url, mysqlCfg.Port, mysqlCfg.Database)
		}
	}

	// 连接到数据库
	mysqlDB, err := gorm.Open(mysql.Open(connStr), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用复数表名
		},
		CreateBatchSize: 2000, // 批量创建批次条数
		PrepareStmt:     true, // 启用预编译
		//ZeroLogger: logger.Default.LogMode(logger.Info),
		Logger: customGormLogger, // 使用自定义的日志器
	})

	if err != nil {
		ZeroLog.Error().AnErr("Failed to connect to Mysql database", err).Msg("")
		return nil, err
	}
	ZeroLog.Info().Msg("Gorm Create Mysql Database connection successful")

	sqlDb, err := mysqlDB.DB()
	if err != nil {
		ZeroLog.Error().AnErr("Failed to Get the Mysql Db===", err).Msg("")
		return nil, err
	}

	setConnPoolProps("mysql", sqlDb, false)

	// 测试连接是否有效（使用Ping）
	if err = sqlDb.Ping(); err != nil {
		ZeroLog.Error().AnErr("Failed to Ping the Mysql Db===", err).Msg("")
		return nil, err
	}

	ZeroLog.Info().Any("状态", sqlDb.Stats()).Msg("Gorm Create Mysql Database Connection successful")
	return mysqlDB, nil
})

// InitGormMysqlConn 初始化gorm的mysql连接
//
// 返回值:
//
//	SecondaryDB: gorm的数据库信息
//	error: 错误信息
//
//goland:noinspection ALL
func InitGormMysqlConn() (SecondaryDB, error) {
	db, err := mysqlOnce()
	return SecondaryDB{db}, err
}

func setConnPoolProps(dbType string, db *sql.DB, poolRecord bool) {
	db.SetMaxIdleConns(10)
	db.SetMaxOpenConns(50)
	db.SetConnMaxLifetime(time.Hour)
	ZeroLog.Info().Msgf("Gorm %s Database Connection Pool Setting success", dbType)

	if poolRecord {
		// 记录连接池状态（1分钟执行一次）
		go func() {
			ticker := time.NewTicker(1 * time.Minute)
			defer ticker.Stop()

			for range ticker.C {
				stats := db.Stats()
				ZeroLog.Info().Msgf("连接池状态,idle==%d,inUse==%d,maxLifetimeClosed==%d,maxOpenConnections==%d,maxIdleTimeClosed==%d",
					stats.Idle,
					stats.InUse,
					stats.MaxLifetimeClosed,
					stats.MaxOpenConnections,
					stats.MaxIdleTimeClosed,
				)
			}
		}()
	}
}
